//=====================================================================
//
//  File:      OleDb_TVP.cpp
//  Summary:   This sample does the following:
// 
//  Creates table-valued parameters using dynamic discovery though IOpenRowset::OpenRowset.
//  Sends table-valued parameter rows using the Pull Model (Aggregated Rowset objects) in the EmployeesRowset
//  class.
//  Sends BLOBs as part of a TVP in the CPhotograph class.
//  Uses custom parameter properties using ISSCommandWithParameters.
//  Shows error handling for SQLNCLI10 errors.
// 
//  Date:      January 11, 2008
//
//---------------------------------------------------------------------
//
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//
//===================================================================== 


#include "stdafx.h"
#include "OLEDBUtils.hpp"

class BaseAggregatingRowset : public IRowset {
public:
   BaseAggregatingRowset(DBCOUNTITEM cTotalRows) : m_cRef(0), m_idxRow(1), m_cTotalRows(cTotalRows), m_pUnkInnerSQLNCLIRowset(NULL) {
      m_hAccessor[0] = 0;
   }

      virtual HRESULT SetupAccessors(IAccessor* pIAccessorTVP) = 0;

      STDMETHODIMP_(ULONG) AddRef() {
         ULONG cRef = InterlockedIncrement((long*)&m_cRef);
         return cRef;
      }

      STDMETHODIMP_(ULONG) Release() {
         assert(m_cRef > 0);

         ULONG cRef = InterlockedDecrement((long *) &m_cRef);

         if (!cRef)
            delete this;

         return cRef;
      }

      // In QueryInterface, delegate to Inner Rowset for anything but IRowset & IUnknown
      STDMETHODIMP QueryInterface (REFIID  riid, LPVOID* ppv ) {
         if (riid == IID_IUnknown) 
            *ppv = static_cast<IUnknown*>(this);
         else {
            // If we are not initialized yet and somebody is asking for non-Unk interface
            if (!m_pUnkInnerSQLNCLIRowset) {
               *ppv = NULL;
               return E_NOINTERFACE;
            }

            if (riid == IID_IRowset) 
               *ppv = static_cast<IUnknown*>(this);
            else
               return m_pUnkInnerSQLNCLIRowset->QueryInterface(riid, ppv);
         }

         (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
         return S_OK;
      }

      STDMETHODIMP AddRefRows (DBCOUNTITEM, const HROW[], DBREFCOUNT[], DBROWSTATUS[]) {
         // Never gets called, so we can return E_NOTIMPL
         return E_NOTIMPL;
      }

      // Read the data from storage, allocate row handles and give 
      // them back to the caller.
      STDMETHODIMP GetNextRows( HCHAPTER, DBROWOFFSET cRowsToSkip, DBROWCOUNT cRows, DBCOUNTITEM* pcRowsObtained, HROW** prghRows) {
         assert(cRowsToSkip == 0);
         assert(cRows == 1);
         assert(*prghRows);

         *pcRowsObtained = 0;

         // If we still have rows to give back
         if (m_idxRow <= m_cTotalRows) {
            *pcRowsObtained = 1;

            // For us, row handle is simply an index in our row list
            HROW* phRows = *prghRows;
            *phRows = m_idxRow;
            m_idxRow++;

            return S_OK;
         }
         else
            return DB_S_ENDOFROWSET;
      }

      // Release data that is not needed corresponding to row handle
      STDMETHODIMP ReleaseRows(DBCOUNTITEM cRows, const HROW rghRows[], DBROWOPTIONS[], DBREFCOUNT[], DBROWSTATUS[]) {
         assert(cRows == 1);
         assert(rghRows[0] <= m_cTotalRows);
         return S_OK;
      }

      STDMETHODIMP GetData(HROW, HACCESSOR hAccessor, void*) {
#ifdef _DEBUG
         DBORDINAL idxAccessor;
         for (idxAccessor = 0; idxAccessor < 1; idxAccessor++) {
            if (m_hAccessor[idxAccessor] == hAccessor)
               break;
         }
         assert(idxAccessor < 1);
#endif
         return S_OK;
      }

      STDMETHODIMP RestartPosition( HCHAPTER) {
         m_idxRow = 1;
         return S_OK;
      }

protected:
   DBCOUNTITEM m_idxRow;
   IUnknown* m_pUnkInnerSQLNCLIRowset;

   // Make the destructor private, so that the object is only creatable on the heap
   virtual ~BaseAggregatingRowset() {
      HRESULT hr = S_OK;
      if (m_hAccessor[0]) {
         IAccessor* pIAccessor = NULL;
         hr = m_pUnkInnerSQLNCLIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessor));
         assert(SUCCEEDED(hr));
         hr = pIAccessor->ReleaseAccessor(m_hAccessor[0], NULL);
         assert(SUCCEEDED(hr));
      }
      ::Release(&m_pUnkInnerSQLNCLIRowset);
   }

   // Save the handle of the accessor that we create, the indexing is 0 based
   void SetAccessorHandle(DBORDINAL idxAccessor, HACCESSOR hAccessor) {
      m_hAccessor[idxAccessor] = hAccessor;
   }

private:
   ULONG m_cRef;
   DBCOUNTITEM  m_cTotalRows;
   // Defining as an array because in general there can be as many accessors as necessary
   // the reading rules from the provider for such scenarios are describe in the Books online
   HACCESSOR m_hAccessor[1];
};

// There is just 1 accessor for this Rowset
class EmployeesRowset : public BaseAggregatingRowset {
private:
   struct EmployeeData {
      DBLENGTH  nameLength;
      DBSTATUS  nameStatus;
      wchar_t   nameValue[50 + 1];
      DBLENGTH  birthdayLength;
      DBSTATUS  birthdayStatus;
      DBDATE    birthdayValue;
      DBLENGTH  salaryLength;
      DBSTATUS  salaryStatus;
      long      salaryValue;
      DBLENGTH  photographLength;
      DBSTATUS  photographStatus;
      IUnknown* photographValue;
   };

protected:
   // Make the destructor private, so that the object is only creatable on the heap
   virtual ~EmployeesRowset() {}

public:
   EmployeesRowset ( DBCOUNTITEM cTotalRows ) : BaseAggregatingRowset(cTotalRows) {
      // For the random number generator, used for producing dummy random data
      srand(123456);
   }

   // Set up aggregator & aggregatee relationship here
   HRESULT Initialize(IOpenRowset* pIOpenRowset) {
      HRESULT hr = S_OK;

      IUnknown* pUnkOuter = static_cast<IUnknown*>(this);
      IAccessor* pIAccessorEmployees = NULL;

      DBID dbidEmployees;
      dbidEmployees.eKind = DBKIND_GUID_NAME;
      dbidEmployees.uGuid.guid = CLSID_ROWSET_TVP;
      dbidEmployees.uName.pwszName = L"tvpEmployees";

      hr = pIOpenRowset->OpenRowset(pUnkOuter, &dbidEmployees, NULL, IID_IUnknown, 0, NULL, &m_pUnkInnerSQLNCLIRowset);
      CHKHR_OLEDB_GOTO(hr, _Exit, pIOpenRowset, IID_IOpenRowset);

      hr = pUnkOuter->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessorEmployees));
      CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IAccessor.");

      hr = SetupAccessors(pIAccessorEmployees);
      CHKHR_GOTO(hr, _Exit);

_Exit:
      ::Release(&pIAccessorEmployees);
      return hr;
   }

   STDMETHODIMP GetData ( HROW hRow, HACCESSOR hAccessor, void* pData ) {
      // The base implementation just does validation, could have possibly
      // been made an abstract virtual function
      BaseAggregatingRowset::GetData(hRow, hAccessor, pData);

      // Use m_hAccessor, to figure out which accessor caller specified, and write the columns data 
      // for columns corresponding to those accessors into pData. Fetch the data into provided buffer, 
      // we will know the format of these accessors, because we created them.

      EmployeeData* pCurrentEmployee = reinterpret_cast<EmployeeData*>(pData);
      FillRowData(pCurrentEmployee);

      return S_OK;
   }
private:

   HRESULT SetupAccessors(IAccessor* pIAccessorEmployees) {
      HRESULT hr = S_OK;

      DBBINDING bindingsEmployees[4];
      FillBindingsAndSetupRowBuffer(bindingsEmployees);

      HACCESSOR hAccessorEmployees;
      DBBINDSTATUS bindStatusEmployees[4] = {DBBINDSTATUS_OK, DBBINDSTATUS_OK, DBBINDSTATUS_OK, DBBINDSTATUS_OK};

      hr = pIAccessorEmployees->CreateAccessor(
         DBACCESSOR_ROWDATA, 
         4, 
         bindingsEmployees, 
         sizeof(EmployeeData),
         &hAccessorEmployees, 
         bindStatusEmployees); 
      CHKHR_OLEDB_GOTO(hr, _Exit, pIAccessorEmployees, IID_IAccessor);

      SetAccessorHandle(0, hAccessorEmployees);
_Exit:
      return hr;
   }

   // This routine does the job of populating data for each row, in real world scenarios, hRow could
   // possibly be passed here, in order to identify the particular row of data & it could be read
   // from some persistent medium like disk/network/UI-controls etc
   void FillRowData(EmployeeData* pCurrentEmployee) {
      pCurrentEmployee->birthdayStatus = DBSTATUS_S_OK;
      pCurrentEmployee->birthdayLength = sizeof(DBDATE);
      pCurrentEmployee->birthdayValue.day   = 15;
      pCurrentEmployee->birthdayValue.month = 5;
      pCurrentEmployee->birthdayValue.year  = 1980;

      pCurrentEmployee->salaryLength = sizeof(long);
      pCurrentEmployee->salaryStatus = DBSTATUS_S_OK;
      pCurrentEmployee->salaryValue  = 100000;

      pCurrentEmployee->nameLength = static_cast<DBLENGTH>(wcslen(L"John Doe") * sizeof(wchar_t));
      pCurrentEmployee->nameStatus = DBSTATUS_S_OK;
      StringCchCopy(pCurrentEmployee->nameValue, sizeof(pCurrentEmployee->nameValue) / sizeof(wchar_t), L"John Doe");

      pCurrentEmployee->photographLength = 2000 + (rand() % 2000);
      pCurrentEmployee->photographStatus = DBSTATUS_S_OK;
      pCurrentEmployee->photographValue  = new CPhotograph(pCurrentEmployee->photographLength);
   }

   void FillBindingsAndSetupRowBuffer(DBBINDING* pBindingsEmployees) {
      for (DBORDINAL i = 0; i < 4; i++) {
         pBindingsEmployees[i].pTypeInfo = NULL;
         pBindingsEmployees[i].pObject = NULL;
         pBindingsEmployees[i].pBindExt = NULL;
         pBindingsEmployees[i].eParamIO = DBPARAMIO_NOTPARAM;
         pBindingsEmployees[i].iOrdinal = i + 1;
         pBindingsEmployees[i].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
         pBindingsEmployees[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
         pBindingsEmployees[i].dwFlags = 0;
      }

      pBindingsEmployees[0].wType = DBTYPE_WSTR;
      pBindingsEmployees[0].cbMaxLen = (50 + 1) * sizeof(wchar_t);
      pBindingsEmployees[0].obLength = offsetof(EmployeeData, nameLength);
      pBindingsEmployees[0].obStatus = offsetof(EmployeeData, nameStatus);
      pBindingsEmployees[0].obValue = offsetof(EmployeeData, nameValue);
      pBindingsEmployees[1].wType = DBTYPE_DBDATE;
      pBindingsEmployees[1].cbMaxLen = sizeof(DBDATE);
      pBindingsEmployees[1].obLength = offsetof(EmployeeData, birthdayLength);
      pBindingsEmployees[1].obStatus = offsetof(EmployeeData, birthdayStatus);
      pBindingsEmployees[1].obValue = offsetof(EmployeeData, birthdayValue);
      pBindingsEmployees[2].wType = DBTYPE_I4;
      pBindingsEmployees[2].cbMaxLen = sizeof(long);
      pBindingsEmployees[2].obLength = offsetof(EmployeeData, salaryLength);
      pBindingsEmployees[2].obStatus = offsetof(EmployeeData, salaryStatus);
      pBindingsEmployees[2].obValue = offsetof(EmployeeData, salaryValue);
      pBindingsEmployees[3].wType = DBTYPE_IUNKNOWN;
      pBindingsEmployees[3].cbMaxLen = sizeof(IUnknown*);
      pBindingsEmployees[3].obLength = offsetof(EmployeeData, photographLength);
      pBindingsEmployees[3].obStatus = offsetof(EmployeeData, photographStatus);
      pBindingsEmployees[3].obValue = offsetof(EmployeeData, photographValue);
   }
};

HRESULT PopulateEmployees(IDBCreateCommand* pIDBCreateCommand, IRowset* pIRowsetEmployees) {	
   HRESULT hr = S_OK;

   // Create the RPC call
   ICommandText* pICommandText = NULL;
   ISSCommandWithParameters* pISSCommandWithParameters = NULL;
   IAccessor* pIAccessorCmd = NULL;
   HACCESSOR hAccessorCmd;
   DBBINDING bindingsCmd  [2] = {0};
   DBBINDSTATUS bindStatusCmd[2] = {DBBINDSTATUS_OK, DBBINDSTATUS_OK};
   DBOBJECT dbObjTVP = {STGM_READ, IID_IRowset};

   hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, reinterpret_cast<IUnknown**>(&pICommandText));
   CHKHR_OLEDB_GOTO(hr, _Exit, pIDBCreateCommand, IID_IDBCreateCommand);

   hr = pICommandText->SetCommandText(DBGUID_DEFAULT, L"{call insertEmployees(?, ?)}");
   CHKHR_OLEDB_GOTO(hr, _Exit, pICommandText, IID_ICommandText);

   hr = pICommandText->QueryInterface(IID_ISSCommandWithParameters, reinterpret_cast<void**>(&pISSCommandWithParameters));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IUnknown for ISSCommandWithParameters.");

   // Give the parameter information to the provider
   const DB_UPARAMS rgParamOrdinalsEmployees[2] = {1, 2};
   DBPARAMBINDINFO rgParamBindInfoEmployees[2] = {0};

   rgParamBindInfoEmployees[0].pwszDataSourceType = L"table";
   rgParamBindInfoEmployees[0].pwszName = L"@tvpEmployees";
   rgParamBindInfoEmployees[0].ulParamSize = ~0UL;
   rgParamBindInfoEmployees[0].dwFlags = DBPARAMFLAGS_ISINPUT;

   rgParamBindInfoEmployees[1].pwszDataSourceType = L"DBTYPE_I4";
   rgParamBindInfoEmployees[1].pwszName = L"@id";
   rgParamBindInfoEmployees[1].ulParamSize = sizeof(long);
   rgParamBindInfoEmployees[1].dwFlags = DBPARAMFLAGS_ISOUTPUT;

   hr = pISSCommandWithParameters->SetParameterInfo(2, rgParamOrdinalsEmployees, rgParamBindInfoEmployees);
   CHKHR_OLEDB_GOTO(hr, _Exit, pISSCommandWithParameters, IID_ISSCommandWithParameters);

   DBPROP ssPropParam [1] = {0};
   DBPROPSET ssPropsetParam [1];
   SSPARAMPROPS ssParamProps [1];

   ssPropParam[0].dwPropertyID = SSPROP_PARAM_TYPE_TYPENAME;
   ssPropParam[0].vValue.vt = VT_BSTR;
   ssPropParam[0].vValue.bstrVal = SysAllocString(L"tvpEmployees");

   ssPropsetParam[0].cProperties = 1;
   ssPropsetParam[0].guidPropertySet = DBPROPSET_SQLSERVERPARAMETER;
   ssPropsetParam[0].rgProperties = ssPropParam;

   ssParamProps[0].cPropertySets = 1;
   ssParamProps[0].iOrdinal = 1;
   ssParamProps[0].rgPropertySets = ssPropsetParam;

   hr = pISSCommandWithParameters->SetParameterProperties(1, ssParamProps);
   SysFreeString(ssPropParam[0].vValue.bstrVal);
   CHKHR_OLEDB_GOTO(hr, _Exit, pISSCommandWithParameters, IID_ISSCommandWithParameters);

   struct PARAMDATA {
      DBLENGTH employeesLength;
      DBSTATUS employeesStatus;
      IUnknown * employeesValue;
      DBLENGTH idLength;
      DBSTATUS idStatus;
      long idValue;
   };

   PARAMDATA cmdParamData;

   hr = pICommandText->QueryInterface(IID_IAccessor, reinterpret_cast<void**>(&pIAccessorCmd));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IUnknown for IAccessor.");

   // Define the binding information
   bindingsCmd[0].wType = DBTYPE_TABLE;
   bindingsCmd[0].cbMaxLen = sizeof(IUnknown*);
   bindingsCmd[0].pObject = &dbObjTVP;
   bindingsCmd[0].eParamIO = DBPARAMIO_INPUT;
   bindingsCmd[0].iOrdinal = 1;
   bindingsCmd[0].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
   bindingsCmd[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
   bindingsCmd[0].obLength = offsetof(PARAMDATA, employeesLength);
   bindingsCmd[0].obStatus = offsetof(PARAMDATA, employeesStatus);
   bindingsCmd[0].obValue = offsetof(PARAMDATA, employeesValue);
   bindingsCmd[1].wType = DBTYPE_I4;
   bindingsCmd[1].cbMaxLen = sizeof(long);
   bindingsCmd[1].pObject = NULL;
   bindingsCmd[1].eParamIO = DBPARAMIO_OUTPUT;
   bindingsCmd[1].iOrdinal = 2;
   bindingsCmd[1].dwPart = DBPART_LENGTH | DBPART_VALUE | DBPART_STATUS;
   bindingsCmd[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
   bindingsCmd[1].obLength = offsetof(PARAMDATA, idLength);
   bindingsCmd[1].obStatus = offsetof(PARAMDATA, idStatus);
   bindingsCmd[1].obValue = offsetof(PARAMDATA, idValue);

   hr = pIAccessorCmd->CreateAccessor(
      DBACCESSOR_PARAMETERDATA, 
      2, 
      bindingsCmd, 
      sizeof(PARAMDATA), 
      &hAccessorCmd, 
      bindStatusCmd);
   CHKHR_OLEDB_GOTO(hr, _Exit, pIAccessorCmd, IID_IAccessor);

   // Fill cmdParamData with parameter values
   cmdParamData.employeesLength = sizeof(IUnknown*);
   cmdParamData.employeesStatus = DBSTATUS_S_OK;
   cmdParamData.employeesValue = pIRowsetEmployees;
   cmdParamData.idLength = sizeof(long);
   cmdParamData.idStatus = DBSTATUS_S_OK;
   cmdParamData.idValue = 0;

   // Execute the command
   DBPARAMS cmdParams;
   cmdParams.cParamSets = 1;
   cmdParams.hAccessor = hAccessorCmd;
   cmdParams.pData = &cmdParamData;

   hr = pICommandText->Execute(NULL, IID_NULL, &cmdParams, NULL, NULL);
   CHKHR_OLEDB_GOTO(hr, _Exit, pICommandText, IID_ICommandText);

   wprintf(L"Employee table population completed. ID : %d.\n", cmdParamData.idValue);

_Exit:
   Release(&pIAccessorCmd);
   Release(&pISSCommandWithParameters);
   Release(&pICommandText);

   return hr;
}

int main() {
   HRESULT hr = S_OK;

   CCOMLoader comLoader;
   CSQLNCLIDataSource dso;

   IOpenRowset*	pIOpenRowset = NULL;
   IDBCreateCommand* pIDBCreateCommand = NULL;
   IRowset* pIRowsetEmployees = NULL;

   hr = comLoader.Load();
   CHKHR_GOTO_MSG(hr, _Exit, L"Unable to Load COM.");

   hr = dso.Connect(L"localhost", L"testdb");
   CHKHR_GOTO(hr, _Exit);

   hr = dso.GetSession(&pIOpenRowset);
   CHKHR_GOTO(hr, _Exit);

   hr = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, reinterpret_cast<void**>(&pIDBCreateCommand));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI for IDBCreateCommand.");

   EmployeesRowset* pEmployeesRowset = new EmployeesRowset(20);
   if (pEmployeesRowset == NULL) {
      hr = E_OUTOFMEMORY;
      CHKHR_GOTO_MSG(hr, _Exit, L"Out of memory.");
   }

   // Do an extra AddRef. This IUnknown will be automatically released by the command execution code
   pEmployeesRowset->AddRef();

   hr = pEmployeesRowset->Initialize(pIOpenRowset);
   CHKHR_GOTO(hr, _Exit);

   hr = pEmployeesRowset->QueryInterface(IID_IRowset, reinterpret_cast<void**>(&pIRowsetEmployees));
   CHKHR_GOTO_MSG(hr, _Exit, L"Failed to QI IRowset for Employees Rowset.");

   hr = PopulateEmployees(pIDBCreateCommand, pIRowsetEmployees);
   CHKHR_GOTO(hr, _Exit);

_Exit:
   Release(&pIRowsetEmployees);
   Release(&pIDBCreateCommand);
   Release(&pIOpenRowset);
   return SUCCEEDED(hr) ? EXIT_SUCCESS : EXIT_FAILURE;
}